package com.sq.imp.Calculator;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.function.Function;

public class ExpressionEvaluator {
    private final String expression;
    public static int save = 5;
    public ExpressionEvaluator(String expression) {
        this.expression = expression;
    }

    public BigDecimal evaluate() {
        LinkedList<String> postfix = transform(expression);
        return calPost(postfix);
    }

    public static BigDecimal calPost(LinkedList<String> post) {
        Stack<BigDecimal> stack = new Stack<>();
        for (String str : post) {
            if ("+-*/^%".contains(str) && !stack.empty()) {
                BigDecimal num2 = stack.pop();
                BigDecimal num1 = stack.pop();
                stack.push(calculateBig(num1, num2, str.charAt(0)));
            } else {
                if (str.charAt(str.length() - 1) == '!') {
                    // 阶乘
                    Function<String, String> f = s -> {
                        if (s.indexOf('.') != -1)
                            s = s.split("\\.")[0];
                        BigInteger num = new BigInteger(s);
                        BigInteger nums = BigInteger.ONE;
                        for (BigInteger i = BigInteger.ONE;
                             i.compareTo(num) <= 0; i = i.add(BigInteger.ONE)) {
                            nums = nums.multiply(i);
                        }
                        return nums.toString();
                    };
                    stack.push(new BigDecimal(f.apply(str.split("!")[0])));
                } else
                    stack.push(new BigDecimal(str));
            }
        }
        return stack.pop();
    }

    public static LinkedList<String> transform(String str) {
        LinkedList<String> postfix = new LinkedList<>();
        Stack<String> symbol = new Stack<>();
        StringBuilder num = new StringBuilder();
        for (int i = 0; i < str.length(); i++) {
            if ("0123456789.!".indexOf(str.charAt(i)) != -1)
                num.append(str.charAt(i));
            else if(' ' != str.charAt(i)){
                if (!num.toString().isEmpty()) {
                    postfix.addLast(num.toString());
                    num = new StringBuilder();
                }
                if (str.charAt(i) == '-' && (i == 0 || "+*/%^(".indexOf(str.charAt(i - 1)) != -1)) {
                    num.insert(0, "-");
                } else if (symbol.empty()) {
                    symbol.push(String.valueOf(str.charAt(i)));
                } else if (str.charAt(i) == ')') {
                    while (!symbol.empty()) {
                        String ch2 = symbol.pop();
                        if (ch2.charAt(0) == '(')
                            break;
                        postfix.addLast(ch2);
                    }
                } else {
                    while (!symbol.empty()) {
                        String ch2 = symbol.pop();
                        if (comparePro(str.charAt(i), ch2.charAt(0)) > 0) {
                            symbol.push(ch2);
                            symbol.push(String.valueOf(str.charAt(i)));
                            break;
                        } else
                            postfix.addLast(ch2);
                    }
                    if (symbol.empty())
                        symbol.push(String.valueOf(str.charAt(i)));
                }
            }
        }
        if (!num.toString().isEmpty())
            postfix.addLast(num.toString());
        while (!symbol.empty())
            postfix.addLast(symbol.pop());
        return postfix;
    }

    public static int comparePro(char ch1, char ch2) {
        if (ch1 == '(' || ch2 == '(')
            return 1;
        else {
            int c1 = 0, c2 = 0;
            LinkedList<String> Symbol = new LinkedList<>();
            Symbol.addLast("+-");
            Symbol.addLast("*/%");
            Symbol.addLast("^");
            for (int i = 0; i < Symbol.size(); i++) {
                if (Symbol.get(i).indexOf(ch1) != -1)
                    c1 = i;
                if (Symbol.get(i).indexOf(ch2) != -1)
                    c2 = i;
            }
            return c1 - c2;
        }
    }

    public static BigDecimal calculateBig(BigDecimal bigDecimal1, BigDecimal bigDecimal2, char op) {
        return switch (op) {
            case '+' -> bigDecimal1.add(bigDecimal2);
            case '-' -> bigDecimal1.subtract(bigDecimal2);
            case '*' -> bigDecimal1.multiply(bigDecimal2);
            case '/' -> bigDecimal1.divide(bigDecimal2, save, BigDecimal.ROUND_HALF_UP);
            case '%' -> bigDecimal1.remainder(bigDecimal2);
            case '^' -> bigDecimal1.pow(bigDecimal2.intValue());
            default -> null;
        };
    }
}